/**
 *  @file gameEngine.cpp
 *
 *  @date 24-nov-2009
 *  @author Ives van der Flaas
 */

#include <iostream>
#include <cassert>
#include <sstream>
#include <typeinfo>
#include "gameEngine.h"
#include "fileNotFoundException.h"
#include "invalidXMLException.h"
#include "levelInfo.h"
#include "tinyxml.h"
#include "collisionDetector.h"
#include "enemyShip.h"
#include "shipDescr.h"

namespace si
{

GameEngine::GameEngine(std::tr1::shared_ptr<ObjectFactory> theObjectFactory):
	fObjectFactory(theObjectFactory)
{
	fTimer = fObjectFactory->getTimer();
	fLevelTimer = fObjectFactory->getTimer();


	fUserShip = fObjectFactory->getFriendlyFighter();
	fShips.reserve(10); // reserve space for 10 ships
	fShips.push_back(fUserShip);

	loadShips();

	reset();


}

GameEngine::~GameEngine()
{
	// TODO Auto-generated destructor stub
}

void GameEngine::reset()
{
	// delete all enemy ships and reset friendly ship
	while(fShips.size() > 1)
		fShips.erase(fShips.begin()+1);

	//Clear all events left in the levels...
	while(fLevelInfo.levels.size() > 0)
		fLevelInfo.levels.pop();

	loadLevels();
	fCurrentLevel = 1;

	fUserShip->resetScore();
	newLevel();

}

void GameEngine::newLevel()
{
	fTimer->reset();
	fLevelTimer->reset();

	// No more levels available! OMG! Just reload levels and hope nobody notices :x
	if(fLevelInfo.levels.size() == 0)
		loadLevels();

	fLevelTitle = fObjectFactory->getLevelTitle(fLevelInfo.levels.front().name);
	fBackground = fObjectFactory->getBackground(fCurrentLevel);

	fCurrentLevel++;
	fUserShip->reset();

}
FrameResult GameEngine::newFrame(double timePassed)
{
	// Check to see if we finished the game
	{
		if(fLevelInfo.levels.size() == 0)
		{
			return WONGAME;
		}


	}
	// Check to see if we finished a level
	{
		// We've won if there are no more parts coming AND...
		if(fLevelInfo.levels.front().parts.size() == 0)
		{
			// if our ship is the only one left...
			if(fShips.size() == 1)
			{
				// Ok, We've won!
				fLevelInfo.levels.pop();
				newLevel();
			}
		}
	}

	// see if we need to place new ships
	makeEnemies();



	///////////////////////////////////////////////////////////////////////////////////////
	///////////////////////// Now draw and update everything //////////////////////////////
	///////////////////////////////////////////////////////////////////////////////////////
	{
		// draw background
		fBackground->draw(si::Vector2(-4,3),si::Vector2(8,6));

		// if the level has been running for less than 5 seconds, display levelname
		if(fLevelTimer->poll() < 5)
			fLevelTitle->draw();

		// update and draw ships & bullets
		for(std::vector<std::tr1::shared_ptr<Ship> >::iterator it = fShips.begin();
				it != fShips.end(); it++)
		{
			(*it)->update(timePassed);
			(*it)->draw();
		}

	}

	// now detect collisions...

	// See if our ship shot enemies or if we got hit...
	for(unsigned int i = 1; i < fShips.size(); i++)
	{
		int damage = 0;
		int whichBullet = -1;
		// See if we shot someone
		if(collisionDetector.aShotB(fShips[0], fShips[i], damage, whichBullet))
		{
			fUserShip->bulletHit(whichBullet);
			fShips[i]->takeHit(damage);	// let the ship that was hit know it was hit
		}

		// Now see if we got hit
		if(collisionDetector.aShotB(fShips[i], fShips[0], damage, whichBullet))
		{
			fUserShip->takeHit(damage);
			fShips[i]->bulletHit(whichBullet);	// let the ship that was hit know it was hit
		}
	}

	// See if ships need to be deleted...
	for(unsigned int i = 1; i < fShips.size(); i++)
	{
		if(fShips[i]->deleteMe())
		{
			fShips.erase(fShips.begin()+i);
			i--;
		}
	}

	// Now see if enemyships have passed into the friendly zone
	for(unsigned int i = 1; i < fShips.size(); i++)
	{
		if(std::tr1::dynamic_pointer_cast<EnemyShip>(fShips[i])->isTooLow())
		{
			return LOSTGAME;
		}
	}

	// If our own ship is dead, we'll end the game
	if(fUserShip->deleteMe())
		return LOSTGAME;



	// Normally, nothing special happened
	return NONE;
}


void GameEngine::makeEnemies()
{
	if(fLevelInfo.levels.size() > 0)
	{
		if(fLevelInfo.levels.front().parts.size() > 0)
		{
			// use RTTI to see what's the next thing to do
			if(typeid(*(fLevelInfo.levels.front().parts.front().get())) == typeid(DelayPart))
			{
				if(fTimer->poll() > dynamic_cast<DelayPart*>(fLevelInfo.levels.front().parts.front().get())->delay)
				{
					fTimer->reset();
					fLevelInfo.levels.front().parts.pop();
				}
			}
			else if(typeid(*(fLevelInfo.levels.front().parts.front().get())) == typeid(WavePart))
			{
				WavePart wave = *(dynamic_cast<WavePart*>(fLevelInfo.levels.front().parts.front().get()));

				double verticalRowOffset = 0;

				for(std::vector<ShipRow>::iterator it = wave.rows.begin(); it != wave.rows.end(); it++ )
				{
					assert(fShipDb.count(it->type) != 0);
					ShipDescr descr = fShipDb[it->type];

					// for x positions, keep 1 clearance at each side of the screen. 8 - 2*1 = 6
					const double scrWidth = 6.0;

					const double spacing = scrWidth / (it->nrOfShips+1);

					// y = max of screen with 0.5 clearance.
					const double y = 3+0.5+verticalRowOffset;

					for(int i = 0; i < it->nrOfShips; i++)
					{
						const double x = 1.0 + (i+1)*spacing - 4;

						fShips.push_back(
								std::tr1::shared_ptr<Ship>(
										fObjectFactory->getEnemyFighter(si::Vector2(x,y), descr)));

					}

					verticalRowOffset += 0.8;
				}

				fLevelInfo.levels.front().parts.pop();
				fTimer->reset();
			}
		}
	}
}

void GameEngine::loadShips()
{
	std::string filename = "config/ships.xml";
	TiXmlDocument ships(filename.c_str());
	if(!ships.LoadFile())
		throw FileNotFoundException(filename);

	TiXmlNode* versionNode = ships.FirstChild();
	TiXmlDeclaration* versionDecl = versionNode->ToDeclaration();

	if(std::string(versionDecl->Version()) != std::string("1.0"))
		throw InvalidXMLException();

	TiXmlElement* root = ships.FirstChildElement("ships");

	TiXmlElement* ship = root->FirstChildElement("ship");

	while(ship != 0)
	{
		std::string shipname = ship->Attribute("name");
		Vector2 size(-1.0, -1.0);
		std::tr1::shared_ptr<ControllerDescr> itsController;

		TiXmlElement* sizeElem = ship->FirstChildElement("size");
		double x,y;
		sizeElem->Attribute("x", &x);
		sizeElem->Attribute("y", &y);
		size.setX(x);
		size.setY(y);


		std::string startHealthStr = ship->FirstChildElement("startHealth")->GetText();
		std::stringstream ss;
		ss << startHealthStr;
		int startHealth = -1;
		ss >> startHealth;

		std::string fireFreqStr = ship->FirstChildElement("fireFreq")->GetText();
		std::stringstream ss2;
		ss2 << fireFreqStr;
		int fireFreq;
		ss2 >> fireFreq;

		TiXmlElement* controllerElem = ship->FirstChildElement("controller");
		std::string controllerName = controllerElem->Attribute("type");

		if(controllerName == "linearcontroller")
		{
			std::string verticalSpeedStr = controllerElem->FirstChildElement("verticalSpeed")->GetText();
			std::string horizontalSpeedStr = controllerElem->FirstChildElement("horizontalSpeed")->GetText();

			std::stringstream ss3;
			ss3 << verticalSpeedStr;
			double verticalSpeed;
			ss3 >> verticalSpeed;

			std::stringstream ss4;
			ss4 << horizontalSpeedStr;
			double horizontalSpeed;
			ss4 >> horizontalSpeed;

			Vector2 speed(horizontalSpeed, verticalSpeed);

			itsController = std::tr1::shared_ptr<ControllerDescr>(new LinearControllerDescr(speed));
		}
		else if(controllerName == "sinusoidalcontroller")
		{
			std::string verticalSpeedStr = controllerElem->FirstChildElement("verticalSpeed")->GetText();
			std::string sweepWidthStr = controllerElem->FirstChildElement("sweepWidth")->GetText();

			std::stringstream ss5;
			ss5 << verticalSpeedStr;
			double verticalSpeed;
			ss5 >> verticalSpeed;

			std::stringstream ss6;
			ss6 << sweepWidthStr;
			double sweepWidth;
			ss6 >> sweepWidth;

			itsController = std::tr1::shared_ptr<ControllerDescr>(new SinusoidalControllerDescr(verticalSpeed, sweepWidth));


		}
		else
		{
			std::cerr << controllerName << " is not a valid controller." << std::endl;
			assert(false);
		}

		ProjectileType projType = BULLET;

		if(TiXmlElement* projElem = ship->FirstChildElement("projectile"))
		{

			std::string projTypeStr = projElem->Attribute("type");
			if(projTypeStr == "sprayingbullet")
				projType = SPRAYINGBULLET;
			else if(projTypeStr == "bullet")
				projType = BULLET;
			else
				assert(false);

		}

		assert(fShipDb.count(shipname) == 0);

		fShipDb[shipname] = ShipDescr(shipname,size,startHealth, fireFreq, projType, itsController);
		ship = ship->NextSiblingElement("ship");
	}


}
void GameEngine::loadLevels()
{
	std::string filename = "config/levels.xml";
	TiXmlDocument levels(filename.c_str());
	if(!levels.LoadFile())
		throw FileNotFoundException(filename);

	TiXmlNode* versionNode = levels.FirstChild();
	TiXmlDeclaration* versionDecl = versionNode->ToDeclaration();

	if(std::string(versionDecl->Version()) != std::string("1.0"))
		throw InvalidXMLException();

	TiXmlNode* levelsNode = levels.FirstChild("levels");
	TiXmlElement* levelsElement = levelsNode->ToElement();

	TiXmlElement* level = levelsElement->FirstChildElement();

	// loop over all levels
	while(level != 0)
	{
		TiXmlElement* waveOrDelay = level->FirstChildElement();

		Level thisLevel;

		// Get This level's name
		thisLevel.name = (level->Attribute("id") ? level->Attribute("id") : "Unknown Level");

		// and all levelparts...
		while(waveOrDelay != 0)
		{
			if(std::string(waveOrDelay->Value()) == std::string("wave"))
			{

				WavePart wave;

				TiXmlElement* shipRow = waveOrDelay->FirstChildElement();

				// pass over all shiprows...
				while(shipRow != 0)
				{
					TiXmlElement* shipRowSpec = shipRow->FirstChildElement();
					ShipRow row;

					// for each shiprow, check out nr of ships and type of ships
					while(shipRowSpec != 0)
					{
						if(std::string(shipRowSpec->Value()) == std::string("nrOfShips"))
						{
							int nrofships;
							std::stringstream s;
							s << shipRowSpec->GetText();
							s >> nrofships;shipRowSpec->Value();

							row.nrOfShips = nrofships;
						}

						if(std::string(shipRowSpec->Value()) == std::string("type"))
						{
							row.type = shipRowSpec->GetText();
						}

						shipRowSpec = shipRowSpec->NextSiblingElement();
					}

					wave.rows.push_back(row);
					shipRow = shipRow->NextSiblingElement();
				}

				thisLevel.parts.push(std::tr1::shared_ptr<WavePart>(new WavePart(wave)));


			 }


			if(std::string(waveOrDelay->Value()) == std::string("delay"))
			{
				int delayTime;

				std::stringstream ss;
				ss << waveOrDelay->GetText();
				ss >> delayTime;

				

				DelayPart delay;
				delay.delay = delayTime;

				thisLevel.parts.push(std::tr1::shared_ptr<DelayPart>(new DelayPart(delay)));

			}

			waveOrDelay = waveOrDelay->NextSiblingElement();
		}

		fLevelInfo.levels.push(thisLevel);
		level = level->NextSiblingElement();
	}
}
}
